package com.example.fill.output;

import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;

/**
 * 通用输出控制
 * 数据组装下沉在OutputType的子类当中
 * @param <T> 数据类型
 * @param <P> 平台类型
 * @param <I> 平台提供的写能力类型
 */
public class CommOutput<T, P, I> extends AsyncTask<P, Integer, Integer> {

    private final List<List<T>> outputData;
    private List<String> title = new ArrayList<>();
    private final List<OutputBeanWrapper<T>> beanDataGetFunc = new ArrayList<>();
    private final List<Function<T, Object>> beanMethodFunc = new ArrayList<>(0);
    private final List<Method> methods = new ArrayList<>();
    private Class<T> reflectClazz;
    public Function<Integer, String> pageTitleMapper = String::valueOf;
    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
    private final OutputType<P, I> outputType;
    private final File targetOutputFile;

    /**
     * 如果不对数据类型的内容做删减，并且不进行日期转换操作，输出完完整整的数据类，请使用这个构造。
     * 注意！实际实现是通过反射拿bean类的getter方法，如果你的bean类没有getter方法就不会有输出；getter方法顺序混乱（不和字段属性顺序一样），输出的结果也会混乱
     * @param context         上下文
     * @param outputData      输出数据，一页是一个List<T>,默认多页
     * @param clazz           数据类型
     * @param fileName        输出的文件名称
     * @param outputType      输出的文件类型
     */
    public CommOutput(Context context, List<List<T>> outputData, Class<T> clazz, List<String> title, String fileName, OutputType<P, I> outputType) {
            File cacheDir = context.getFilesDir();

        if (!cacheDir.exists()){
            boolean mkdir = cacheDir.mkdir();
            Log.i("TAG", "CommOutput: "+mkdir);

        }
        targetOutputFile = new File(cacheDir.getAbsolutePath(), fileName + outputType.getType());
        if (!targetOutputFile.exists()){
            try {
                boolean newFile = targetOutputFile.createNewFile();
                Log.i("TAG", "CommOutput: "+newFile);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        this.outputData = outputData;
        this.outputType = outputType;
        this.reflectClazz = clazz;
        this.title.addAll(title);
        outputType.setContext(context).setFile(targetOutputFile);
    }

    /**
     * 需要对bean类输出的字段做删减，则提供OutputBeanWrapper包装对象输出对应属性
     * @param context         上下文
     * @param outputData      输出数据，一页是一个List<T>,默认多页
     * @param beanDataGetFunc 数据包装类，包含数据bean类的转换到表格当中的标题与提取对象的数据，数据转换操作
     * @param fileName        输出的文件名称
     * @param outputType      输出的文件类型
     */
    public CommOutput(Context context, List<List<T>> outputData, List<OutputBeanWrapper<T>> beanDataGetFunc, String fileName, OutputType<P, I> outputType) {
        File cacheDir = context.getFilesDir();
        if (!cacheDir.exists()){
            boolean mkdir = cacheDir.mkdir();
            Log.i("TAG", "CommOutput: "+mkdir);

        }
        targetOutputFile = new File(cacheDir.getAbsolutePath(), fileName + outputType.getType());
        if (!targetOutputFile.exists()){
            try {
                boolean newFile = targetOutputFile.createNewFile();
                Log.i("TAG", "CommOutput: "+newFile);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        this.outputData = outputData;
        this.beanDataGetFunc.addAll(beanDataGetFunc);
        this.outputType = outputType;
        outputType.setContext(context).setFile(targetOutputFile);
    }

    @SafeVarargs
    @Override
    protected final Integer doInBackground(P... ps) {
        P platform = ps[0];
        if (platform == null) return -1;
        initTitle();
        for (int i = 0; i < outputData.size(); i++) {
            List<T> ts = outputData.get(i);
            I platformCapacity = outputType.getPlatformCapacity(platform, pageTitleMapper.apply(i), i);
            outputType.beforeWrite(platform, platformCapacity);

            outputType.initTitle(platformCapacity, title);
            List<List<String>> dataOfPage;
            if (beanDataGetFunc.isEmpty()){
                dataOfPage = expandDataWithReflect(ts,reflectClazz);
            }else {
                dataOfPage = expandData(ts);
            }
            for (int j = 0; j < dataOfPage.size(); j++) {
                outputType.initBody(platformCapacity, j + 1, dataOfPage.get(j));
            }
            outputType.releasePage(platformCapacity);
        }
        outputType.notifyWriteFinished();
        outputType.releasePlatform(platform);
        return 0;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected void onPostExecute(Integer integer) {
        outputType.onPostExecute(integer);
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
    }

    private List<List<String>> expandData(List<T> beanOfPage) {
        List<List<String>> dataOfPage = new ArrayList<>();
        List<String> rowOfPage = new ArrayList<>();
        for (int i = 0; i < beanOfPage.size(); i++) {
            rowOfPage.clear();
            for (OutputBeanWrapper<T> tOutputBeanWrapper : beanDataGetFunc) {
                if (tOutputBeanWrapper.needTransform()) {
                    sdf.applyPattern(tOutputBeanWrapper.getPatternTrans());
                    rowOfPage.add(sdf.format(tOutputBeanWrapper.getMethodFunction().apply(beanOfPage.get(i))));
                } else {
                    rowOfPage.add(String.valueOf(tOutputBeanWrapper.getMethodFunction().apply(beanOfPage.get(i))));
                }
            }
            dataOfPage.add(rowOfPage);
        }
        return dataOfPage;
    }

    private List<List<String>> expandDataWithReflect(List<T> beanOfPage,Class<T> clazz) {
        if (beanMethodFunc.isEmpty()) initBeanMethod(clazz);
        if (beanMethodFunc.size() != methods.size()) throw new IllegalArgumentException();
        List<List<String>> dataOfPage = new ArrayList<>();
        List<String> rowOfPage = new ArrayList<>();
        for (int i = 0; i < beanOfPage.size(); i++) {
            T t = beanOfPage.get(i);
            rowOfPage.clear();
            for (int j = 0; j < beanMethodFunc.size(); j++) {
                Function<T, Object> tObjectFunction = beanMethodFunc.get(j);
                Object data = tObjectFunction.apply(t);
                rowOfPage.add(String.valueOf(data));
            }
            dataOfPage.add(rowOfPage);
        }
        return dataOfPage;
    }

    /**
     * 如不进行配置Bean类的包装类型，就走这个反射拿数据
     * @param clazz 数据类型
     */
    private void initBeanMethod(Class<T> clazz) {
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            beanMethodFunc.add(t -> {
                declaredField.setAccessible(true);
                try {
                    return declaredField.get(t);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            });
        }
//        Method[] allMethods = clazz.getDeclaredMethods();
//        for (Method method : allMethods) {
//            if (method.getName().startsWith("get") || method.getName().startsWith("is")){
//                this.methods.add(method);
//                beanMethodFunc.add(t -> {
//                    try {
//                        return method.invoke(t,null);//勿需强转，否则错误
//                    } catch (IllegalAccessException | InvocationTargetException e) {
//                        throw new RuntimeException(e);
//                    }
//                });
//            }
//        }
    }

    private void initTitle() {
        for (OutputBeanWrapper<T> tOutputBeanWrapper : beanDataGetFunc) {
            title.add(tOutputBeanWrapper.getColumnName());
        }
        title = outputType.onTitleInitialCompletion(title);
    }

    public void execute() {
        super.execute(outputType.createPlatform(targetOutputFile));
    }


}
